package com.hunttown.common.dao.datasource.config;

import com.hunttown.common.dao.datasource.MasterSlaveDataSource;
import com.hunttown.common.dao.sequence.Sequence;
import com.hunttown.common.dao.sequence.Sequences;

import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

public class SequenceBeanDefinitionParser implements BeanDefinitionParser {
    private static final Logger logger = LoggerFactory.getLogger(SequenceBeanDefinitionParser.class);
    private final Class MASTER_SLAVE_DATASOURCE_CLASS = MasterSlaveDataSource.class;
    private final Class SEQUENCES_CLASS = Sequences.class;
    private final Class SEQUENCE_CLASS = Sequence.class;
    private final int DATASOURCE_INIT_SIZE = 5;
    private final int DATASOURCE_MAX_SIZE = 100;

    public SequenceBeanDefinitionParser() {
    }

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        logger.debug("sequences config : ==========start all sequences config==========");
        List<Element> sequencesElements = DomUtils.getChildElementsByTagName(element, "sequences");
        this.parseSequences(sequencesElements, parserContext);
        logger.debug("datasource-config : ==========end all sequences config==========");
        return null;
    }

    private void parseSequences(List<Element> sequencesElements, ParserContext parserContext) {
        Iterator i$ = sequencesElements.iterator();

        while (i$.hasNext()) {
            Element sequencesElement = (Element) i$.next();
            this.parseSequences(sequencesElement, parserContext);
        }

    }

    private void parseSequences(Element sequencesElement, ParserContext parserContext) {
        String beanName = sequencesElement.getAttribute("id");
        String defaultSequenceName = sequencesElement.getAttribute("default-sequence-name");
        String defaultDatasourceBeanName = sequencesElement.getAttribute("default-datasource-ref");
        String defaultSequences = sequencesElement.getAttribute("default");
        logger.debug("sequences config : ==========start config==========");
        logger.debug("sequences config : create sequences bean name : {}", beanName);
        logger.debug("sequences config : default sequence name : {}", defaultSequenceName);
        logger.debug("sequences config : is default sequences : {}", defaultSequences);
        if (StringUtils.isNotBlank(defaultDatasourceBeanName)) {
            boolean notExistsDefaultDatasourceBean = !parserContext.getRegistry().containsBeanDefinition(defaultDatasourceBeanName);
            if (notExistsDefaultDatasourceBean) {
                throw new IllegalArgumentException("sequences config : named [" + beanName + "] sequences tag attribute [default-datasource-ref] " + "must ref an exists bean, but not found datasource bean [" + defaultDatasourceBeanName + "]");
            }

            logger.debug("sequences config : default datasource : {}, master datasource : {}", defaultDatasourceBeanName, this.getMasterDataSourceBeanName(defaultDatasourceBeanName, parserContext));
        }

        List<Element> sequenceElements = DomUtils.getChildElementsByTagName(sequencesElement, "sequence");
        ManagedMap<String, RuntimeBeanReference> sequenceMap = new ManagedMap();
        Iterator i$ = sequenceElements.iterator();

        while (i$.hasNext()) {
            Element sequenceElement = (Element) i$.next();
            String name = sequenceElement.getAttribute("name");
            if (sequenceMap.containsKey(name)) {
                throw new IllegalArgumentException("sequences config : named [" + beanName + "] sequences had define an same named [" + name + "] sequence");
            }

            sequenceMap.put(name, new RuntimeBeanReference(this.parseSequence(beanName, defaultDatasourceBeanName, sequenceElement, parserContext)));
        }

        RootBeanDefinition sequencesDefinition = new RootBeanDefinition(this.SEQUENCES_CLASS);
        if (StringUtils.isNotBlank(defaultSequenceName)) {
            if (!sequenceMap.containsKey(defaultSequenceName)) {
                throw new IllegalArgumentException("sequences config : named [" + beanName + "] sequences tag attribute [default-sequence-name] " + "must ref an exists sequence, but not found sequence [" + defaultSequenceName + "]");
            }

            sequencesDefinition.getPropertyValues().add("defaultSequence", sequenceMap.get(defaultSequenceName));
        }

        sequencesDefinition.getPropertyValues().add("sequenceMap", sequenceMap);
        parserContext.getRegistry().registerBeanDefinition(beanName, sequencesDefinition);
        if ("true".equalsIgnoreCase(defaultSequences)) {
            parserContext.getRegistry().registerAlias(beanName, "defaultSequences");
        }

        logger.debug("sequences config : ==========end config==========");
    }

    private String parseSequence(String sequencesName, String defaultDatasourceBeanName, Element sequenceElement, ParserContext parserContext) {
        String name = sequenceElement.getAttribute("name");
        String startValue = sequenceElement.getAttribute("start-value");
        String blockSize = sequenceElement.getAttribute("block-size");
        String dataSourceBeanName = sequenceElement.getAttribute("datasource-ref");
        if (StringUtils.isBlank(dataSourceBeanName)) {
            dataSourceBeanName = defaultDatasourceBeanName;
        }

        boolean notExistsDatasourceBean = StringUtils.isBlank(dataSourceBeanName) || !parserContext.getRegistry().containsBeanDefinition(dataSourceBeanName);
        if (notExistsDatasourceBean) {
            throw new IllegalArgumentException("sequences config : under the named [" + sequencesName + "] sequences tag of the named [" + name + "] sequence tag must ref an exists datasource bean, " + "but not found datasource bean [" + dataSourceBeanName + "]");
        } else {
            String masterDataSourceBeanName = this.getMasterDataSourceBeanName(dataSourceBeanName, parserContext);
            logger.debug("sequences config : create sequence name : {}, start-value : {}, block-size : {}, datasource : {}, master datasource : {}", new Object[]{name, startValue, blockSize, dataSourceBeanName, masterDataSourceBeanName});
            RootBeanDefinition sequenceDefinition = new RootBeanDefinition(this.SEQUENCE_CLASS);
            MutablePropertyValues propertyValues = sequenceDefinition.getPropertyValues();
            propertyValues.add("startValue", startValue);
            propertyValues.add("blockSize", blockSize);
            propertyValues.add("dataSource", new RuntimeBeanReference(masterDataSourceBeanName));
            String beanName = BeanDefinitionReaderUtils.generateBeanName(sequenceDefinition, parserContext.getRegistry());
            parserContext.getRegistry().registerBeanDefinition(beanName, sequenceDefinition);
            return beanName;
        }
    }

    private String getMasterDataSourceBeanName(String datasourceBeanName, ParserContext parserContext) {
        BeanDefinition datasourceDefinition = parserContext.getRegistry().getBeanDefinition(datasourceBeanName);
        if (this.MASTER_SLAVE_DATASOURCE_CLASS.getName().equals(datasourceDefinition.getBeanClassName())) {
            PropertyValues propertyValues = datasourceDefinition.getPropertyValues();
            datasourceBeanName = (String) propertyValues.getPropertyValue("masterDataSourceBeanName").getValue();
        }

        return datasourceBeanName;
    }
}